#pragma once 

#include <string>
#include <iostream>
#include <jsoncpp/json/json.h>
#include <algorithm>

#include "index.hpp"
#include "util.hpp"

namespace ns_searcher 
{
    typedef struct inverted_element_print
    {
        uint64_t doc_id;
        int weight;
        //保存搜索的不同关键字,但是对应到了同一个文档id
        std::vector<std::string> words;

        inverted_element_print() : doc_id(0), weight(0){}
    }inver_elem_p_t;

    class searcher
    {
    public:
        void init_searcher(const std::string& input_src_path)
        {
            //1.获取或者创建index对象
            index = ns_index::index::get_instance();
            LOG(NORMAL, "获取index对象成功!");

            //根据index对象建立索引
            index->build_index(input_src_path);
            LOG(NORMAL, "index创建成功!");
        }

        //query : 进行搜索的关键字
        //json_string : 返回给用户浏览器的搜索结果
        void search(const std::string& query, std::string* json_string)
        {
            //1.对query先进行分词
            std::vector<std::string> words;
            ns_util::jieba_util::cut_string(query, &words);

            //2.根据分出的各个词,进行index查找
            //我们建立index(倒排)的时候是忽略大小写的
            //所以搜索的时候我们也要忽略大小写
            //ns_index::inverted_list inverted_list_all;//这里内部是inverted_element的一个vector
            
            std::vector<inver_elem_p_t> inverted_list_all;

            std::unordered_map<uint64_t, inver_elem_p_t> tokens_map;

            for (auto word : words)
            {
                boost::to_lower(word);

                ns_index::inverted_list* inverted_list = index->get_inverted_list(word);
                if (inverted_list == nullptr)
                {
                    continue;
                }

                //注意我们这里的inverted_list_all是可能会有重复的的inverted_element
                //形成这个的原因是可能搜索的关键字进行分词之后,有两个或以上的词获得倒排拉链中有相同的inverted_element
                //inverted_list_all.insert(inverted_list_all.end(), inverted_list->begin(), inverted_list->end());
                
                for (const auto& elem : *inverted_list)
                {
                    //item一定是doc_it相同的print节点
                    auto& item = tokens_map[elem.doc_id];
                    item.doc_id = elem.doc_id;
                    item.weight += elem.weight;
                    item.words.push_back(elem.word);
                }
            }

            for(const auto& item : tokens_map)
            {
                inverted_list_all.push_back(std::move(item.second));
            }

            //3.汇总查找结果,按照相关性(weight)降序排列
            //std::sort(inverted_list_all.begin(), inverted_list_all.end(),
            //        [](const ns_index::inver_elem_t& elem1, const ns_index::inver_elem_t elem2)->bool
            //        {
            //            return  elem1.weight > elem2.weight;
            //        }
            //        );
            
            std::sort(inverted_list_all.begin(), inverted_list_all.end(),
                    [](const inver_elem_p_t& elem1, const inver_elem_p_t& elem2)->bool
                    {
                        return elem1.weight > elem2.weight;
                    });

            //4.根据合并之后的inverted_list_all,构建json串
            //通过jsoncpp完成序列化和反序列化
            Json::Value root;
            for (auto& inver_elem_t : inverted_list_all)
            {
                ns_index::doc_info_t* doc = index->get_forward_index(inver_elem_t.doc_id);
                if (doc == nullptr)
                {
                    continue;
                }

                Json::Value inver_elem;
                inver_elem["title"] = doc->title;

                //注意:content是文档去掉标签的结果,但是不是我们需要的
                //我们需要的是内容的一部分,也就是摘要
                inver_elem["description"] = get_description(doc->content, inver_elem_t.words[0]);
                inver_elem["url"] = doc->url;
                //json会自动把int转换成string的
                //PS:我们这里使用的uint64,需要进行强转,json不支持uint64转成string
                inver_elem["id"] = (int)inver_elem_t.doc_id;
                inver_elem["weight"] = inver_elem_t.weight;
                root.append(inver_elem);
            }
            Json::FastWriter writer;
            *json_string = writer.write(root);
        }

    private:
        std::string get_description(const std::string& html_content, const std::string& word)
        {
            //找到word在html_content中首次出现的位置,然后往前截取50个字节(如果前面没有50个字节,就从头开始截取),再向后截取100个字节(如果后面没有,就截取到尾部)
            const size_t prev_step =  50;
            const size_t next_step = 100;

            //1.找到word首次出现的位置
            //注意我们这里拿的是原始html网页的数据,也就是大小写我们没有进行改变
            //但是我们这里用来进行搜索的数据,却是我们转换成为了小写
            //所以这里有可能是搜索不到数据的
            //所以我们这里使用search函数来进行查找
            //可以通过仿函数来进行控制,从而让我们忽略大小写的去进行查找
            //size_t pos = html_content.find(word);
            //if (pos == std::string::npos)
            //{
            //    return "find none!";
            //}
            
            auto it = std::search(html_content.begin(), html_content.end(), word.begin(), word.end(), [](char x, char y)->bool{
                    return (std::tolower(x) == std::tolower(y));
                    });

            if (it == html_content.end())
            {
                return "find none!";
            }

            size_t pos = std::distance(html_content.begin(), it);

            //2.获取start和end
            size_t start = 0;
            size_t end = html_content.size() - 1;

            //我们这里使用的是无符号整数,所以我们这里使用pos - prev_step可能是一个超级大的正数
            //和我们的预期不符,所以我们可以使用int类型解决问题
            //也可以进行参数位置的调整
            //if (pos - prev_step > start)
            //{
            //    start = pos - prev_step;
            //}
            
            if (pos > start + prev_step)
            {
                start = pos - prev_step;
            }

            if (pos + next_step < end)
            {
                end = pos + next_step;
            }

            if (start >= end)
            {
                return "start >= end, none!";
            }

            //3.截取字串,然后进行返回
            std::string ret_desription = html_content.substr(start, end - start);
            ret_desription += "......";
            return ret_desription;
        }

    private:
        ns_index::index* index;
    };

}
